﻿using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.VisualStudio.ProjectSystem.Properties;

namespace Red.Scripts.Project.Utility
{
    internal static class CommandlineBuilder
    {
        #region Private Types

        /// <summary>
        /// A collection of command line arguments that can be resolved into a single string to be passed as the command line when starting a new process
        /// </summary>
        private class Arguments
        {
            #region Prvate Types

            private class Argument
            {
                public string Name { get; set; }
                public string Prefix { get; set; }

                public override string ToString()
                {
                    return $"{Prefix}{Name}";
                }
            }

            private class ValuedArgument : Argument
            {
                public string Value { get; set; }

                public override string ToString()
                {
                    return $"{Prefix}{Name}=\"{Value}\"";
                }
            }

            #endregion Prvate Types

            #region Private Data

            private const string DefaultPrefix = "-";

            private List<Argument> _arguments = new List<Argument>();

            #endregion Private Data

            #region Public Properties

            public string Formatted => string.Join( " ", _arguments );

            #endregion Public Properties

            #region Public Methods

            public void AddArgument( string argument, string value, string prefix = DefaultPrefix )
            {
                _arguments.Add( new ValuedArgument() { Name = argument, Value = value, Prefix = prefix } );
            }

            public void AddSwitch( string argument, string prefix = DefaultPrefix )
            {
                _arguments.Add( new Argument() { Name = argument, Prefix = prefix } );
            }

            #endregion Public Methods
        }

        #endregion Private Types

        #region Public Functions

        public static async Task<string> ConstructLaunch( ProjectProperties properties )
        {
            DefinesAccessor defines = new DefinesAccessor();
            await defines.Load();

            return await ConstructLaunch( properties, defines );
        }

        public static async Task<string> ConstructLaunch( ProjectProperties properties, DefinesAccessor defines )
        {
            Arguments arguments = new Arguments();

            ConstructRoots( defines, arguments );
            await AddWorld( properties, defines, arguments );
            await AddQuest( properties, defines, arguments );

            var debuggerProperties = await properties.GetRedScriptsDebuggerPropertiesAsync();
            await AddSwitch( debuggerProperties.RedRunInWindow, arguments );
            await AddArgument( debuggerProperties.RedWindowWidth, arguments );
            await AddArgument( debuggerProperties.RedWindowHeight, arguments );
            await AddArgument( debuggerProperties.RedEngineType, arguments );

            RedScriptsProjectDeploy deployProperties = await properties.GetRedScriptsProjectDeployPropertiesAsync();
            await AddArgument( deployProperties.CameraLocation, arguments );
            await AddArgument( deployProperties.CameraRotation, arguments );

            string customArguments = await debuggerProperties.RedScriptsCommandLineArguments.GetEvaluatedValueAtEndAsync();

            if ( string.IsNullOrWhiteSpace( customArguments ) )
            {
                return arguments.Formatted;
            }

            return $"{arguments.Formatted} {customArguments}";
        }

        public static async Task<string> ConstructDeploy( ProjectProperties properties, DefinesAccessor defines )
        {
            Arguments arguments = new Arguments();

            ConstructRoots( defines, arguments );
            await AddWorld( properties, defines, arguments );

            arguments.AddArgument( "commandlet", "generate-world" );

            return arguments.Formatted;
        }

        #endregion Public Functions

        #region Private Functions

        private static async Task AddSwitch( IEvaluatedProperty property, Arguments arguments )
        {
            if ( property.IncludeInCommandLine && ( await property.GetValueAsync() ).Equals( true ) )
            {
                arguments.AddSwitch( property.Switch, property.SwitchPrefix );
            }
        }

        private static async Task AddArgument( IEvaluatedProperty property, Arguments arguments )
        {
            if ( property.IncludeInCommandLine )
            {
                string value = await property.GetEvaluatedValueAtEndAsync();

                if ( !string.IsNullOrWhiteSpace( value ) )
                {
                    arguments.AddArgument( property.Switch, value, property.SwitchPrefix );
                }
            }
        }

        private static async Task AddWorld( ProjectProperties properties, DefinesAccessor defines, Arguments arguments )
        {
            RedScriptsProjectDeploy deployProperties = await properties.GetRedScriptsProjectDeployPropertiesAsync();

            string world = await GetDepotPath( deployProperties.World, defines );
            if ( !string.IsNullOrEmpty( world ) )
            {
                arguments.AddArgument( "world", world );
            }
        }

        private static async Task AddQuest( ProjectProperties properties, DefinesAccessor defines, Arguments arguments )
        {
            RedScriptsProjectDeploy deployProperties = await properties.GetRedScriptsProjectDeployPropertiesAsync();

            string quest = await GetDepotPath( deployProperties.Quest, defines );
            if ( !string.IsNullOrEmpty( quest ) )
            {
                arguments.AddArgument( "startQuest", quest );
            }
        }

        private static async Task<string> GetDepotPath( IEvaluatedProperty property, DefinesAccessor defines )
        {
            string absolutePath = await property.GetEvaluatedValueAtEndAsync();

            if ( !string.IsNullOrWhiteSpace( absolutePath ) && absolutePath != property.DefaultValue )
            {
                string workspacePath = defines.ActiveWorkspaceRoot;

                if ( !string.IsNullOrEmpty( workspacePath ) )
                {
                    PathConverter converter = new PathConverter();
                    await converter.Load( workspacePath );

                    return converter.ToResourcePath( absolutePath );
                }
            }

            return null;
        }

        private static void ConstructRoots( DefinesAccessor defines, Arguments arguments )
        {
            string packagePath = defines.ActiveEnginePackagePath;
            string workspacePath = defines.ActiveWorkspaceRoot;

            if ( !string.IsNullOrEmpty( packagePath ) )
            {
                string path = System.IO.Path.Combine( packagePath, "engine" );
                arguments.AddArgument( "engineRoot", path );
            }
            else if ( !string.IsNullOrEmpty( workspacePath ) )
            {
                string path = System.IO.Path.Combine( workspacePath, "engine" );
                arguments.AddArgument( "engineRoot", path );
            }

            if ( !string.IsNullOrEmpty( workspacePath ) )
            {
                string path = System.IO.Path.Combine( workspacePath, "r6" );
                arguments.AddArgument( "gameRoot", path );
            }
        }

        #endregion Private Functions
    }
}
